%%@doc The nod module represents a Node Agent. The bot spawns 54 Node Agents when starting a game and it holds information about the board. 
%%@end
-module(nod).
-export([init/1,searchHex/3,nod/8,checkResources/4]).

%%@doc The init function sets the information to calculate adjacent tiles and nodes and spawns the nod function.
%%@spec init(NodeId) -> Pid
%%@end
init(Id) -> 
    case (Id rem 2) of
	    0	-> spawn(fun() -> nod(Id,[Id-33,Id+1,Id-1],[{Id-17,0},{Id-15,0},{Id+17,0}]
                ,[],[],[],{-1,-1},false) end);
		_Else	-> spawn(fun() -> nod(Id,[Id-18,Id-16,Id+16],[{Id-17,0},{Id+15,0},{Id+17,0}],[],[],[],{-1,-1},false) end)
	end.

%%@doc nod receives messages from the Actuator . It updates the environment around the node, including the robber position, and calculates its value.
%%@spec nod(AgentIds,Tiles,AdjNodes,Roads,Value,Resource,Built,Blocked) -> ok
%%@end
nod(Id,Tiles,Adjnodes,Roads,Value,Resource,Built,Blocked) -> 
	receive
		{board,Board,Nodes} -> 
                           ValRes = searchHex(Tiles,Board,[]),
						   {Val,Res} = lists:unzip(ValRes), 
						   nod(Id,Tiles,matchIdPid(Adjnodes,Nodes),Roads,Val,Res,Built,Blocked);
        {addRoads,NewRoads,Nodes} -> 
            AdjRoads = (if
               (Id div 2) == 0 -> checkRoads([Id-17,Id-16,Id],NewRoads++Nodes);
               true -> checkRoads([Id-17,Id-1,Id],NewRoads++Nodes)
            end),
            io:fwrite("\tNode: roads is added ~w~n",[AdjRoads]),
            nod(Id,Tiles,Adjnodes,AdjRoads,Value,Resource,Built,Blocked);
		{nodevalue,Pid,Val,TileRes} -> 
            TileResInc = setTileRes(TileRes,Resource), 
           if
                (Built /= {-1,-1}) or Blocked -> Pid ! {nodeval,false};
                true  -> 
                    NVal = checkResources(lists:sum(Value),
                                lists:zip(Resource,Value),TileRes,TileResInc),
                    if
                        NVal > Val ->
                            Pid ! {nodeval,true,Id,NVal};
                        true ->
                            Pid ! {nodeval,false}
                    end
           end,
           nod(Id,Tiles,Adjnodes,Roads,Value,Resource,Built,Blocked);
		{resources,Pid} -> Pid ! {res,Id,Resource},
						   nod(Id,Tiles,Adjnodes,Roads,Value,Resource,Built,Blocked);
        {haveHarbor,Pid} ->  
            ClayH  = haveHarbor(Resource,[17,49,65,81,97]),
            OreH   = haveHarbor(Resource,[18,50,66,82,98]),
            SheepH = haveHarbor(Resource,[19,51,67,83,99]),
            WheetH = haveHarbor(Resource,[20,52,68,84,100]),
            WoodH  = haveHarbor(Resource,[21,53,69,85,101]),
            ThreeH = haveHarbor(Resource,[7,8,9,10,11,12]),
            if
                ClayH  -> Pid ! {haveHarborRespons,clay};
                OreH   -> Pid ! {haveHarborRespons,ore};
                SheepH -> Pid ! {haveHarborRespons,sheep};
                WheetH -> Pid ! {haveHarborRespons,wheet};
                WoodH  -> Pid ! {haveHarborRespons,wood};
                ThreeH -> Pid ! {haveHarborRespons,threeOne};
                true   -> Pid ! {haveHarborRespons,false}
            end;
        {incomeRes,TileRes,Pid} -> 
            if
                ((Built == {0,1}) or (Built == {0,2})) -> 
                    Pid ! {incRes,setTileRes(TileRes,Resource)};
                true -> Pid ! {incRes,TileRes}
            end,
			nod(Id,Tiles,Adjnodes,Roads,Value,Resource,Built,Blocked);

        {nodeBuilt,Owner,Type} -> lists:map(fun({_,Pid})-> Pid!{adjNodeBuilt} end,Adjnodes),
                           nod(Id,Tiles,Adjnodes,Roads,Value,Resource,{Owner,Type},true);
        {roadBuilt,RId,Owner} when RId == Id ->
            io:fwrite("\tNod: Road added with same Id as node and with owner: ~w~n",[Owner]),
            UpRoads = lists:keyreplace(RId,1,Roads,{RId,Owner}),
            case bot:getValue(Id+17,Adjnodes) of
                false -> false;
                NId -> NId!{roadBuilt,RId,Owner}
            end,
            nod(Id,Tiles,Adjnodes,UpRoads,Value,Resource,Built,Blocked);
        {roadBuilt,RId,Owner} -> 
            io:fwrite("\tNod: Road added with different Id as node~n"),
            UpRoads = lists:keyreplace(RId,1,Roads,{RId,Owner}),
            nod(Id,Tiles,Adjnodes,UpRoads,Value,Resource,Built,Blocked);
        {adjNodeBuilt}  -> nod(Id,Tiles,Adjnodes,Roads,Value,Resource,Built,true);
        {nodeOwner,Pid} -> 
            {Owner,Type} = Built,
            Pid ! {awnsNodeOwner,Owner,Type};
        {findBNode,Pid} -> 
            io:fwrite("\tNode: entering findBNode, Roads: ~w~n",[Roads]),
            {RId,NId} = findRoad(Roads,Adjnodes), 
            NId ! {findBNodeRoad,Id,self()},
            Awnser = receive {awnsBNodeRoad,RId,NId,Val} -> {RId,NId,Val} end,
            Pid ! {awnsBNode,Awnser};
        {findBNodeRoad,From,Pid} ->
            io:fwrite("Nodes: ~w~nRoads: ~w~n",[Adjnodes,Roads]),
            {RId,NId,Val} = getBestAdjnode(Adjnodes,Roads,From,{0,0,0}),
            Pid ! {awnsBNodeRoad,RId,NId,Val};
        {robberInfo,Pid} -> if 
                                Built /= {-1,-1} -> Pid!{nodeInfo,Built};
                                true  -> Pid!{nodeInfo,{-1,-1}}
                            end,
                            nod(Id,Tiles,Adjnodes,Roads,Value,Resource,Built,Blocked);
        {buildValue,Type,PID} -> 
            if
                ((Built == {0,1}) and (Type == 2))  or  
                ((Built == {-1,-1}) and (Type == 1) and (not Blocked)) ->
                    PID ! {settlement, Id,
                        checkResources(lists:sum(Value),
                        lists:zip(Resource,Value),[0,0,0,0,0],[0,0,0,0,0])};
                true -> PID ! {settlement, false}
            end;
        X -> io:fwrite("~w~n",[X])
	end,
    nod(Id,Tiles,Adjnodes,Roads,Value,Resource,Built,Blocked).


getBestAdjnode([],[],_,BestNode) -> BestNode;
getBestAdjnode([_|Roads],[{From,NId}|Nodes],From,BestNode) -> 
    getBestAdjnode(Roads,Nodes,From,BestNode);
getBestAdjnode([{RId,Owner}|Roads],[{Node,NId}|Nodes],From,{R,N,BVal}) ->
    if 
        Owner =< 0 -> 
            NId ! {buildValue,1,self()},
            receive 
                {settlement,false} -> getBestAdjnode(Roads,Nodes,From,{R,N,BVal});
                {settlement,Id,Val} -> 
                    if 
                        Val > BVal -> getBestAdjnode(Roads,Nodes,From,{RId,Node,Val});
                        true -> getBestAdjnode(Roads,Nodes,From,{R,N,BVal})
                    end
            end;
        true -> getBestAdjnode(Roads,Nodes,From,{R,N,BVal})
    end.

findRoad([{RId,0}|Roads],[{_,NId}|Nodes]) -> {RId,NId};
findRoad([Road|Roads],[_|Nodes]) -> 
    io:fwrite("\tNode: current road: ~w~n",[Road]),
    findRoad(Roads,Nodes).

haveHarbor(Res,Placing) ->
    lists:member(true,lists:map(fun(Re) -> lists:member(Re,Placing) end,Res)).

setTileRes(TileRes,[]) -> TileRes;
setTileRes(TileRes,[R|Res]) ->
    if 
        (R > 0) and (R < 6) ->
            setTileRes(lists:sublist(TileRes,R-1)++
                [lists:nth(R,TileRes)+1]++
                lists:nthtail(R,TileRes),Res);
        true -> setTileRes(TileRes,Res)
    end.

%%@doc checkResources weight specific resources for our strategy.
%%@spec checkResources(ValueOfNode,EmptyNewNodeValue) -> NewNodeValue
%%@end
checkResources(Val,[],_,_) -> Val;
checkResources(Val,[{Re,Va}|Res],TileRes,TileResInc) ->
        TRVal = checkTileRes(Re,TileRes) + checkTileResH(Re,TileResInc),
		case Re of
			2 -> case Va of
					2 -> checkResources(Val+0.5+TRVal,Res,TileRes,TileResInc);
					3 -> checkResources(Val+2+TRVal,Res,TileRes,TileResInc);
					_ -> checkResources(Val+3+TRVal,Res,TileRes,TileResInc)
				 end;
			3 -> case Va of
					2 -> checkResources(Val+TRVal,Res,TileRes,TileResInc);
					3 -> checkResources(Val+0.5+TRVal,Res,TileRes,TileResInc);
					_ -> checkResources(Val+1+TRVal,Res,TileRes,TileResInc)
				 end;
			4 -> case Va of
					2 -> checkResources(Val+TRVal,Res,TileRes,TileResInc);
					3 -> checkResources(Val+0.5+TRVal,Res,TileRes,TileResInc);
					_ -> checkResources(Val+2+TRVal,Res,TileRes,TileResInc)
				 end;
			_ -> checkResources(Val+TRVal,Res,TileRes,TileResInc)
		end.

%% Checks if ore,sheep or wheet is not taken if so increase current nodes value
%% if the current resources is missing
checkTileRes(Re,TileRes) ->
    if 
        (Re > 0) and (Re < 6) ->
            case lists:nth(Re,TileRes) of
                0 -> if (Re > 1) and (Re < 5) -> 1.5; true -> 0 end;
                _ -> 0
            end;
        true -> 0
    end.

%% Checks if we have more then 2 resources of any kind then increase value with 6
%% Lower for 3:1 harbors.
checkTileResH(Re,TileRes) ->
    ClayH  = lists:member(Re,[17,49,65,81,97]) and (lists:nth(1,TileRes) > 1),
    OreH   = lists:member(Re,[18,50,66,82,98]) and (lists:nth(2,TileRes) > 1),
    SheepH = lists:member(Re,[19,51,67,83,99]) and (lists:nth(3,TileRes) > 1),
    WheetH = lists:member(Re,[20,52,68,84,100]) and (lists:nth(4,TileRes) > 1),
    WoodH  = lists:member(Re,[21,53,69,85,101]) and (lists:nth(5,TileRes) > 1),
    if 
        (Re > 6) and (Re < 13) ->
            Max = lists:max(TileRes),
            if
                Max > 1  -> 4;
                true     -> 2
            end;
        ClayH or OreH or SheepH or WheetH or WoodH -> 6;
        true -> 0
    end.

%%@doc searchHex returns the value of the adjacent tiles.
%%@spec searchHex(Tiles,Board,emptyResourceValues) -> ResourceValues
%%@end
searchHex([],_,ValRes) -> ValRes;
searchHex([Tile|Tiles],Board,ValRes) ->
		{_Id,Res,Val} = lists:keyfind(Tile,1,Board),
		searchHex(Tiles,Board,[{convertVal(Val),Res}|ValRes]).

convertVal(-1) -> 0;
convertVal(X) when X > 4 -> (X+3) - ((X+3)-7)*2;
convertVal(X) -> X+2.

checkRoads([],_) -> [];
checkRoads([Id|Ids],Roads) ->
    case lists:member(Id,Roads) of
        false -> checkRoads(Ids,Roads);
        true -> [{Id,-1}|checkRoads(Ids,Roads)]
    end.

matchIdPid([],_) -> [];
matchIdPid([{Id,_}|AdjNodes],Nodes) ->
	case lists:keyfind(Id,1,Nodes) of
		{_,Pid} -> [{Id,Pid}|matchIdPid(AdjNodes,Nodes)];
		false	-> matchIdPid(AdjNodes,Nodes)
	end.
